9.4 EdeficePolicyPinGenerator PIN generation algorithm
The EdeficePolicyPinGenerator PIN generation algorithm is similar to the EdeficePinGenerator PIN generation algorithm, but takes into account the PIN character settings configured on the credential profile. The algorithm uses the card serial number as diversification data. The PIN generation key is used to generate the PIN. If you have the card serial number, the same key that is used within MyID, the details of the PIN character settings in the credential profile, and the details of the following algorithm, you can generate the same PINs as MyID.
Alternatively, you can use the user's logon name as the diversification data; this ensures that the user has the same PIN for all of their cards. To use the logon name, set the Use logon name for server PIN generation option on the PINs page of the Security Settings workflow.
Note: If the credential profile has a PIN policy that allows only numeric characters, the EdeficePolicyPinGenerator PIN generation algorithm generates the same results as the EdeficePinGenerator algorithm.
9.4.1 Generating the PIN
The process for generating the PIN is as follows:
-
Use the card serial number as the input to a SHA1 hash.
This generates a 20-byte hash value.
- Truncate the 20-byte hash to the first 16 bytes. Encryption is carried out on 8-byte blocks, so we want to carry out the encryption on two blocks without padding.
-
Encrypt the hash with the PIN generation key.
- Use 3DES encryption in cipher block chaining mode. This generates a 16-byte hex value.
- You do not want any header information in the encrypted data.
- For the initialization vector, use 8 bytes of 0x00.
- Do not use any padding.
-
For each byte, divide by the alphabet size (which is determined by the PIN policy in the credential profile) and take the remainder; in other words, <byte> modulo <alphabet size>.
As there are 16 bytes, you can generate PINs up to 16 characters long. If the PIN is 6 characters long, for example, perform this operation on the first 6 bytes in the encrypted data.
Note: If you need to insert any mandatory characters (see below), reduce the length of the PIN accordingly; for example, for an 8-character PIN, if both symbols and numbers are mandatory, generate a 6-character PIN. You will insert extra characters for the mandatory character types later to reach the full PIN length.
-
Use this value as a look-up in the alphabet table – see section 9.4.2, Alphabet tables.
For example, if the byte is 2C, and the alphabet size is 32 (for a PIN that contains numbers and symbols):
2C = 44 decimal
44 modulo 32 = 12
Entry 12 in the alphabet table = '\'
-
Insert any mandatory characters.
If the PIN policy has more than one allowed character type, and the PIN policy has one or more mandatory character types, you must insert one character for each mandatory type. For example, if lower and upper case characters are optional, but numeric and symbol characters are mandatory, insert one number and one symbol.
If the PIN policy has only one type of character allowed (for example, only numeric) do not insert any mandatory characters; the PIN is already guaranteed to contain characters of that type.
To generate the mandatory characters, use the same method as for the other characters (take the next byte in the encrypted hash, and use it as an index into the alphabet table), but use the specific alphabet table for the category; for example, for mandatory symbol characters use the symbol table.
We do not want to append the mandatory characters to the end of the PIN; this is insufficiently random. To determine the position of the inserted character, take a byte from the end of the encrypted hash, and use this modulo the current PIN size plus one to determine the insertion point. For a second mandatory character, use the second-last byte from the encrypted hash, and so on.
For example, if the last byte is 36, and the current PIN length is 6 characters:
36 = 54 decimal
54 modulo 7 = 5
You can then insert the character at position 5 (this is zero indexed; if the PIN is currently six characters, modulo 7 produces a value between 0 and 6, where 0 means inserting the character at the start, and 6 means inserting the character at the end).
You must insert the mandatory characters in the following order:
-
Numeric
-
Lower alpha
-
Upper alpha
-
Symbol
-
9.4.2 Alphabet tables
The alphabet table is generated from the PIN policy, and may comprise the following:
-
Numeric
-
Lower alpha
-
Upper alpha
-
Symbol
If your PIN policy allows more than one character type, combine the tables in the above order.
9.4.2.1 Numeric
The numeric alphabet has size 10, and the following entries:
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Value |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
For example, a lookup of 0 returns 0, and a lookup of 7 returns 7.
9.4.2.2 Lower alpha
The lower alpha alphabet has size 26, and has the following entries:
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Value |
a | b | c | d | e | f | g | h | i | j |
Index |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Value |
k | l | m | n | o | p | q | r | s | t |
Index |
20 |
21 |
22 |
23 |
24 |
25 |
||||
Value |
u | v | w | x | y | z |
For example, a lookup of 0 returns a, and a lookup of 7 returns h.
9.4.2.3 Upper alpha
The upper alpha alphabet has size 26, and has the following entries:
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Value |
A | B | C | D | E | F | G | H | I | J |
Index |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Value |
K | L | M | N | O | P | Q | R | S | T |
Index |
20 |
21 |
22 |
23 |
24 |
25 |
||||
Value |
U | V | W | X | Y | Z |
For example, a lookup of 0 returns a, and a lookup of 7 returns H.
9.4.2.4 Symbol
The symbol alphabet has size 22, and has the following entries:
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Value |
! | \ | " | # | $ | % | & | ' | ( | |
Index |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Value |
) | * | + | - | . | / | : | ; | = | ? |
Index |
20 |
21 |
||||||||
Value |
@ | ^ |
For example, a lookup of 0 returns a space character, and an lookup of 7 returns &.
9.4.2.5 Combined table
For example, for a PIN policy that requires numbers and symbols, the combined table would look like:
Index |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
Value |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Index |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Value |
! | \ | " | # | $ | % | & | ' | ( | |
Index |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
Value |
) | * | + | - | . | / | : | ; | = | ? |
Index |
30 |
31 |
||||||||
Value |
@ | ^ |
You must combine tables in the following order:
-
Numeric
-
Lower alpha
-
Upper alpha
-
Symbol
9.4.3 Example
If a PIN with a length of eight characters is requested for a card with serial number 1034, and the PIN policy allows numeric, lower alpha, upper alpha, and symbols, with both numeric and symbol characters mandatory, the process is as follows:
-
The card serial number, 1034, is hashed using SHA1 to produce:
290448489A06C6A2DC62E82491212444BD6E341F
-
This is then shortened to 16 bytes, as we want to encode two whole 8-byte blocks:
290448489A06C6A2DC62E82491212444
-
This hash is then 3DES CBC mode encrypted using a shared key to produce, for example:
1F60F51F7D6FF3C316E006C7D239DA36
-
The PIN required is eight characters, but there are two categories of mandatory characters, so create a 6-digit PIN to begin with. You will insert two mandatory characters later to produce a PIN of the required length.
All four categories of character are allowed (numeric, lower alpha, upper alpha, and symbols) so the alphabet table is a combination of all four, 84 characters in total.
The first six bytes from the encrypted hash are used to look up into the alphabet array (size 84). This results in:
Byte 1 of the encrypted string is 1F (hex) = 31 (dec) mod 84 = 31
Character 31 in the alphabet table is v
Byte 2 of the encrypted string is 60 (hex) = 96 (dec) mod 84 = 12
Character 12 in the alphabet table is c
Byte 3 of the encrypted string is F5 (hex) = 245 (dec) mod 84 = 77
Character 77 in the alphabet table is /
Byte 4 of the encrypted string is 1F (hex) = 31 (dec) mod 84 = 31
Character 31 in the alphabet table is v
Byte 5 of the encrypted string is 7D (hex) = 125 (dec) mod 84 = 41
Character 41 in the alphabet table is F
Byte 6 of the encrypted string is 6F (hex) = 111 (dec) mod 84 = 27
Character 27 in the alphabet table is r
The initial six-character PIN is:
vc/vFr
-
Insert the mandatory characters.
There are two mandatory character types in the eight-digit PIN, so the seventh and eighth bytes of the encrypted hash are used to determine the characters to use, and the last and penultimate bytes of the encrypted hash are used to determine the insertion positions of these characters.
To add the mandatory numeric character:
Byte 7 of the encrypted string is F3 (hex) = 243 (dec) mod 10 = 3
Character 3 in the numeric alphabet table is 3
For position, take the last byte of the encrypted hash, and find the modulo of 7 (the current PIN size plus 1):
Byte 16 of the encrypted string is 36 (hex) = 54 (dec) mod 7 = 5
Inserting 3 into vc/vFr at position 5 (zero indexed) produces a seven-digit PIN of:
vc/vF3r
To add the mandatory symbol character:
Byte 8 of the encrypted string is C3 (hex) = 195 (dec) mod 22 = 19
Character 19 in the symbol alphabet table is ?
For position, take the second-last byte of the encrypted hash, and find the modulo of 8 (the current PIN size plus 1):
Byte 15 of the encrypted string is DA (hex) = 218 (dec) mod 8 = 2
Inserting ? into vc/vF3r at position 2 (zero indexed) produces a final PIN of:
vc?/vF3r
This PIN matches the PIN policy in the credential profile – it is eight characters long, includes both symbols and numbers as mandatory characters, and allows both upper and lower case alpha characters.
9.4.3.1 C# example
The following is sample code that generates PINs using C#. The default settings in the sample code produce the same result as the worked example above.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
namespace PINGeneration
{
class Program
{
static void Main(string[] args)
{
char[] numeric = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
char[] alphaLower = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
char[] alphaUpper = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
char[] symbols = { ' ', '!', '\\', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', '-', '.', '/', ':', ';', '=', '?', '@', '^' };
// The policy values are:
// 0 - Optional
// 1 - Mandatory
// 2 - Not allowed
int numericPinPolicy = 1;
int lowercasePinPolicy = 0;
int uppercasePinPolicy = 0;
int symbolsPinPolicy = 1;
// Configure the available characters
StringBuilder allowedSymbolsBuilder = new StringBuilder();
char[] alphabet = { };
if (numericPinPolicy != 2)
{
alphabet = alphabet.Concat(numeric).ToArray();
allowedSymbolsBuilder.Append('N');
}
if (lowercasePinPolicy != 2)
{
alphabet = alphabet.Concat(alphaLower).ToArray();
allowedSymbolsBuilder.Append('L');
}
if (uppercasePinPolicy != 2)
{
alphabet = alphabet.Concat(alphaUpper).ToArray();
allowedSymbolsBuilder.Append('U');
}
if (symbolsPinPolicy != 2)
{
alphabet = alphabet.Concat(symbols).ToArray();
allowedSymbolsBuilder.Append('S');
}
var allowedSymbols = allowedSymbolsBuilder.ToString();
// Only need to configure mandatory character set if more than one character type is allowed in the PIN
StringBuilder mandatorySymbolsBuilder = new StringBuilder();
if (allowedSymbols.Length > 1)
{
if (numericPinPolicy == 1)
mandatorySymbolsBuilder.Append('N');
if (lowercasePinPolicy == 1)
mandatorySymbolsBuilder.Append('L');
if (uppercasePinPolicy == 1)
mandatorySymbolsBuilder.Append('U');
if (symbolsPinPolicy == 1)
mandatorySymbolsBuilder.Append('S');
}
var mandatorySymbols = mandatorySymbolsBuilder.ToString();
// Encryption key
byte[] key = { 0x31, 0x28, 0x7A, 0x5A, 0x36, 0x26, 0x35, 0x31, 0x32, 0x71, 0x71, 0x53, 0x3D, 0x2F, 0x33, 0xA7, 0x21, 0x4C, 0x3F, 0x61, 0x44, 0x31, 0x55, 0x38 };
//Data to be encoded - device serial number
string data = "1034";
//Convert to a byte array
Encoding ascii = Encoding.ASCII;
byte[] databytes = ascii.GetBytes(data);
// Create SHA1 hash of data
SHA1 shaM = new SHA1Managed();
byte[] hash = SHA1Managed.Create().ComputeHash(Encoding.Default.GetBytes(data));
byte[] hash16 = new byte[16];
// Copy the first 16 bytes of the hash array
Array.Copy(hash, hash16, 16);
// Set the initialisation vector to 8 bytes of 0x0
byte[] iv = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
string ciphertext = "";
string pin = "";
string hashhex = "";
string hashhex16 = "";
// Set encryption options.
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
des.KeySize = 192;
des.Key = key;
des.Mode = CipherMode.CBC;
des.Padding = PaddingMode.None;
des.IV = iv;
// Encrypt hashed data
ICryptoTransform ic = des.CreateEncryptor();
byte[] enc = ic.TransformFinalBlock(hash, 0, 16);
for (int i = 0; i < enc.Length; i++)
{
ciphertext = ciphertext + enc[i].ToString("X2");
}
for (int i = 0; i < hash.Length; i++)
{
hashhex = hashhex + hash[i].ToString("X2");
}
for (int i = 0; i < hash16.Length; i++)
{
hashhex16 = hashhex16 + hash16[i].ToString("X2");
}
// Generate PIN from the ciphertext
int pinLength = 8;
int initialPinLength = pinLength - mandatorySymbols.Length;
for (int x = 0; x < initialPinLength; x++)
{
pin = pin + alphabet[Convert.ToInt32(ciphertext.Substring(x * 2, 2), 16) % alphabet.Length];
}
// Now insert the mandatory characters into the pin
for (int index = 0; index < mandatorySymbols.Length; index++)
{
// Do not use the same hash values for selection of both the initial PIN characters and the mandatory characters
var y = index + initialPinLength;
char character = ' ';
switch (mandatorySymbols[index])
{
case 'N':
character = numeric[Convert.ToInt32(ciphertext.Substring(y * 2, 2), 16) % numeric.Length];
break;
case 'L':
character = alphaLower[Convert.ToInt32(ciphertext.Substring(y * 2, 2), 16) % alphaLower.Length];
break;
case 'U':
character = alphaUpper[Convert.ToInt32(ciphertext.Substring(y * 2, 2), 16) % alphaUpper.Length];
break;
case 'S':
character = symbols[Convert.ToInt32(ciphertext.Substring(y * 2, 2), 16) % symbols.Length];
break;
}
// Determine where to insert this character. Use the seed from reverse to determine the character position.
int position = Convert.ToInt32(ciphertext.Substring(ciphertext.Length - index * 2 - 2, 2), 16) % (pin.Length + 1);
pin = pin.Insert(position, character.ToString());
}
Console.WriteLine("Sample PIN generation algorithm output");
Console.WriteLine();
Console.WriteLine("Alphabet size: " + alphabet.Length);
Console.WriteLine("Allowed character set: " + allowedSymbols);
Console.WriteLine("Mandatory character set: " + mandatorySymbols);
Console.WriteLine("Required PIN length: " + pin.Length);
Console.WriteLine("Data: " + data);
Console.WriteLine("SHA1 hash of data: " + hashhex);
Console.WriteLine("16 bytes of hash: " + hashhex16);
Console.WriteLine("Encrypted: " + ciphertext);
Console.WriteLine("PIN: " + pin);
Console.WriteLine("\nPress any key to continue…");
Console.ReadKey(true);
}
}
}